<?php

namespace App\Http\Requests;

use App\Exceptions\ApiErrCode;
use App\Exceptions\ApiException;
use Illuminate\Contracts\Validation\Validator as ContractValidator;
use Illuminate\Database\Query\Builder;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Validator;

/**
 * Class ApiRequest
 *
 * @property string|null per_page
 * @property string|null page
 * @property string|null $languages
 * @property string|null $sort_column
 * @property string|null $sort_direction
 *
 * @package App\Http\Requests
 */
class ApiRequest extends FormRequest
{
    const MAX_INT = 2147483647; // 字段类型是int时，可以提交的最大值
    const MAX_STRING = 255;     // 字符串类型的值，最大字符数255
    const MAX_URL = 2048;     // 字符串 URL 类型的值，最大字符数 2048 字节

    const ERROR_INT_DEFAULT = 0; // 错误的数字

    protected $errors_glue = " ";

    /**
     * @var int
     */
    protected $auth_error_code = ApiErrCode::AUTHORIZE_ERROR_ID_UNKNOWN;

    /**
     * @var string
     */
    protected $auth_error_msg = "Api authorization failed error";
    
    /**
     * @var Validator
     */
    protected $validator;
    
    /**
     * @param ContractValidator $validator
     *
     * @throws ApiException
     * @throws \App\Exceptions\ApiSilentException
     */
    protected function failedValidation(ContractValidator $validator)
    {
        throw new ApiException(
            ApiErrCode::REQUEST_PARAMS_ERROR,
            $this->errorToString(
                $validator->errors()->messages(),
                $this->errors_glue
            )
        );
    }
    
    /**
     * @throws ApiException
     * @throws \App\Exceptions\ApiSilentException
     */
    protected function failedAuthorization()
    {
        throw new ApiException($this->auth_error_code, $this->auth_error_msg);
    }


    /**
     * 数组错误信息转成分隔开的字符串
     *
     * @test
     * @see WechatRequestTest::test_wx_req_format_errors_to_string()
     *
     * @param array $rules_errors
     * @param string $glue
     * @return string
     */
    public function errorToString(array $rules_errors, string $glue)
    {
        return array_reduce($rules_errors, function ($before, $current) use ($glue) {
            $prefix = $before ? $glue : '';
            return $before . $prefix . implode($glue, $current);
        }, '');
    }

    /**
     * @param $conditions
     *
     * @return \Closure
     */
    protected function closureWhere(array $conditions): \Closure
    {
        return function (Builder $query) use ($conditions) {
            return $query->where($conditions);
        };
    }

    /**
     * @param int $code
     * @param string $msg
     */
    protected function setAuthError(int $code, string $msg)
    {
        $this->auth_error_code = $code;
        $this->auth_error_msg = $msg;
    }

    /**
     * @param array $except_keys
     *
     * @return array
     */
    public function extractByRules(array $except_keys = [])
    {
        $no_dot_rules = collect($this->rules())->filter(function ($val, $key) {
            return ! str_contains($key, '.');
        })->all();

        $keep_rules = array_except($no_dot_rules, $except_keys);

        return $this->only(array_keys($keep_rules));
    }

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * @return array
     */
    public function rules()
    {
        return [];
    }
    
    /**
     * @param Validator $validator
     */
    protected function withValidator(Validator $validator)
    {
        $this->validator = $validator;
        
        $validator->after(function (Validator $validator) {
            if ($validator->errors()->isEmpty()) {
                $this->afterValidator($validator);
            }
        });
    }
    
    /**
     * if rules check passed, you can use validated params safely and validator utils
     *
     * @param Validator $validator
     */
    protected function afterValidator(Validator $validator)
    {
        // your own validate logic
    }
    
    /**
     * @param $attribute
     * @param $rule
     * @param $params
     * @return ApiRequest
     */
    protected function validatorAddFailure($attribute, $rule, $params = []): self
    {
        $this->validator->addFailure($attribute, $rule, $params);
        
        return $this;
    }
    
    /**
     * @param $attribute
     * @param $rule
     * @param $params
     * @throws ApiException
     * @throws \App\Exceptions\ApiSilentException
     */
    protected function validatorFailed(string $attribute, string $rule, $params = []): void
    {
        if (! $this->validator) {
            throw new ApiException(ApiErrCode::CUSTOMIZE_ERROR,'Method should call after Dependency Injection and Validator resolved.');
        }
        
        $this->validatorAddFailure($attribute, $rule, $params)
            ->failedValidation($this->validator);
    }
}
